#include <linux/module.h>		// module_init  module_exit   ， 这个头文件就在kernel的include 下 
#include <linux/init.h>			// __init   __exit   ， 这个头文件就在kernel的include 下 ，通过sourceInsight来看
                            // 上面2个头文件写驱动的必须文件，下面的这些用到了才加也可以

#include <linux/fs.h>
// 驱动的头文件就是内核源码目录下 include目录下的头文件
// gcc 编译程序的时候，头文件路径在/usr/include下， gcc工具安装的时生成的

#include <linux/kdev_t.h>
#include <linux/cdev.h>   //cdev 
#include <linux/device.h>   // udev 操作函数
#include <linux/uaccess.h>   // copy_to_user 的头文件

#include <asm/string.h>     // memset头文件 
#include <linux/ioport.h>  // 动态内存映射头文件
#include <asm/io.h>




#define majorName "test_chrdev_open_init"
#define pGPJ0CON *((volatile unsigned int *)S5PV210_GPJ0CON)
#define pGPJ0DAT *((volatile unsigned int *)S5PV210_GPJ0DAT)
#define MY_MAJOR_ID 250
#define MY_COUNT 2


//  目的：北京讯为，框架封装，一个主设备号，2个次设备号设计框架 
//  私有数据使用， 一个主设备，多个次设备号代码 , 了解一下就行了  
// 设备结构体， 把dev_t  cdev 放入一个结构体中进行封装，这个结构体描述一个设备，设备结构体

struct device_test{
    int majorId ;          // 主设备号
    int minorId ;          //次设备号
    dev_t mydev_t; 
    struct class *test_class;
    struct device *device ;      
    struct cdev p_cdev;  // 里面有file_operation结构和 dev_t
    char kbuf[100];     
};


struct device_test dev1;  // 定义2个设备
struct device_test dev2;  



static int test_chrdev_open(struct inode *inode, struct file *file)
{
   
    // dev1.minorId=0;
    // dev2.minorId=1;

// 私有数据如何知道指dev1 还是 dev2 
// 通过 container_of函数， 功能，通过结构体变量中某个成员的首地址p_cdev   获取到整个结构体首地址  device_test  
// 参数1： ptr 是结构体变量中某个成员的首地址  p_cdev 
// 参数2： 参数type是结构体类型 
// 参数3：  member是该结构体变量的其他名字



//打开设备节点的时候，cdev会保存到inode中
//结构体struct file 的一个属性  	void			*private_data;
    file->private_data= container_of(inode->i_cdev,struct device_test,p_cdev);
	// 应用层调用open 函数 
  	printk(KERN_INFO "test_chrdev_open  \n");
  	return 0; 
};


static int test_chrdev_release( struct inode *inode, struct file *file )
{
    printk(KERN_INFO "test_chrdev_release \n");
	  return 0;
};

//把数据从内核空间拷贝到用户空间
static ssize_t test_chrdev_read( struct file *file,char __user *ubuf, size_t len,loff_t *offset )
{

struct device_test* test_dev=(struct device_test*)file->private_data;
// 通过 test_dev 访问主设备号

//char kbuf[32]="this is denganzhi";
    int ret = -1;
    printk(KERN_INFO "test_chrdev_read \n");
    ret =  copy_to_user(ubuf,test_dev->kbuf,len);
    if(ret){
    	 printk(KERN_INFO "copy_to_user fail =%d \n" ,ret);
    	 return -EINVAL;
    }
     printk(KERN_INFO "copy_to_user suc  =%d \n" ,ret);
    return 0;
}

// 应用程序写函数
// const char __user *buffer  是用户空间传递过来
static ssize_t test_chrdev_write( struct file *file,const char __user *ubuf ,size_t len,loff_t *offset )
{
  int ret = -1;


  struct device_test* test_dev=(struct device_test*)file->private_data;

  printk(KERN_INFO "test_chrdev_write \n");
if(test_dev->minorId == 0){
    printk(KERN_INFO "my is  dev1  \n");
}else if(test_dev->minorId ==1){
    printk(KERN_INFO "my is  dev2  \n");
}



    memset(test_dev->kbuf,0,sizeof(test_dev->kbuf));

     // 如果复制成功那么返回0， 如果不成功复制则返回尚未复制成功复制剩下的字节数
    ret= copy_from_user(test_dev->kbuf,ubuf,len);
    if(ret){
    	 printk(KERN_INFO "copy_from_user fail =%d \n" ,ret);
    	 return -EINVAL;
    }
    printk(KERN_INFO "copy_from_user suc: %s",test_dev->kbuf);
    return 0;
}

/* File operations struct for character device */
// 驱动名字 _fops  
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,     //惯例 , 默认 
	.open		= test_chrdev_open,  // 将来应用open打开这个设备是实际调用的函数 
	.release	= test_chrdev_release,  // 
	.write      = test_chrdev_write,
	.read       = test_chrdev_read, 
};



// 模块安装函数   
static int __init chrdev_init(void)
{	
  int retval; 

	printk(KERN_INFO "chrdev_init helloworld init\n");

   //  1.2 自动分配主设备号   次设备号从0开始，2个
   retval = alloc_chrdev_region(&dev1.mydev_t, 0, MY_COUNT, majorName);
   if(retval < 0){
       printk(KERN_INFO "alloc_chrdev_region , majorName=%s   \n", majorName); 
       return -EINVAL; 
      // goto flag1;
   }


   dev1.majorId=MAJOR(dev1.mydev_t);
   dev1.minorId=MINOR(dev1.mydev_t);
   printk(KERN_INFO "alloc_chrdev_region suc");
   printk(KERN_INFO "major= %d ,minor= %d ", dev1.majorId , dev1.minorId ); 
   // mknod  /dev/driver_test  c 250 12



 // 测试dev1
   dev1.p_cdev.owner=THIS_MODULE;
   cdev_init(&dev1.p_cdev, &test_fops);
   retval = cdev_add(&dev1.p_cdev, dev1.mydev_t, 1);   // cdev、设备号、数量    dev_t 和 test_fops 结构体绑定
   if (retval) {
    printk(KERN_ERR "cdev_add fail ");
      return -EINVAL;
   }   
  
      dev1.test_class = class_create(THIS_MODULE, "driver_test1");
      if (IS_ERR(dev1.test_class)) {
        return -EINVAL; 
      }
      dev1.device=device_create(dev1.test_class, NULL, dev1.mydev_t, NULL, "driver_test1");
      printk(KERN_INFO "cdev_add1  suc   \n");


// 测试dev2
   dev2.majorId=MAJOR(dev1.mydev_t);   //主设备需要+1吗????? 
   dev2.minorId=MINOR(dev1.mydev_t+1);
   printk(KERN_INFO "major2= %d ,minor2= %d ", dev2.majorId , dev2.minorId ); 
 
   dev2.p_cdev.owner=THIS_MODULE;
   cdev_init(&dev2.p_cdev, &test_fops);
   cdev_add(&dev2.p_cdev, dev1.mydev_t+1, 1);


  dev2.test_class = class_create(THIS_MODULE, "driver_test2");
  dev2.device=device_create(dev2.test_class, NULL, dev1.mydev_t+1, NULL, "driver_test2");
  printk(KERN_INFO "cdev_add2  suc   \n");

	  return 0;
}



// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
//	unregister_chrdev(majorId, majorName);

   device_destroy(dev1.test_class,dev1.mydev_t);
   class_destroy(dev1.test_class);
 // 1. 注销字符设备驱动 cdev  上面add了，这个要del 
 // cdev_del(&test_cdev);
  cdev_del(&dev1.p_cdev);   // 释放p_cdev的堆内存
  //  2. 上面注册主次设备号， 这里unregister_
  unregister_chrdev_region(dev1.mydev_t,1);


   device_destroy(dev2.test_class,dev2.mydev_t+1);
   class_destroy(dev2.test_class);
   cdev_del(&dev2.p_cdev);   
   unregister_chrdev_region(dev1.mydev_t+1,1);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息

MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息